package org.toycode.solr.service;

import com.chenlb.mmseg4j.Dictionary;
import com.chenlb.mmseg4j.example.Complex;
import org.toycode.solr.extractor.PropertyExtractor;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.bind.JAXBException;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.embedded.EmbeddedSolrServer;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.client.solrj.response.UpdateResponse;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.core.CoreContainer;
import org.toycode.solr.entity.AbstractEntity;
import org.toycode.solr.config.Config;
import org.toycode.solr.config.Prop;
import org.toycode.solr.config.Entity;
import org.toycode.solr.extractor.impl.MSExcelExtractor;
import org.toycode.solr.extractor.impl.MSWordExtractor;
import org.toycode.solr.extractor.impl.PdfExtractor;
import org.toycode.solr.extractor.impl.StringExtractor;
import org.toycode.solr.extractor.impl.TextFileExtractor;
import org.xml.sax.SAXException;

/**
 * This class provides indexing and searching services.
 *
 * @author <a href="mailto:DL88250@gmail.com">Liang Ding</a>
 * @version 1.0.0.6, Aug 28, 2009
 */
final public class SolrService {

    /**
     * the single instance of this service.
     */
    private static SolrService instance;
    /**
     * Solr embedded server.
     */
    private EmbeddedSolrServer server;
    /**
     * field(property) content extractors. 
     */
    private List<PropertyExtractor> extractors;
    /**
     * word segment. 
     */
    private Complex spliter;

    /**
     * Searches using the specified solr query.
     *
     * @param query the specified solr query
     * @return query response
     */
    public QueryResponse search(final String query) {
        final SolrQuery solrQuery = new SolrQuery();
        solrQuery.setQuery(query);
        solrQuery.setHighlight(true);
        final QueryResponse rsp;

        try {
            return server.query(solrQuery);
        } catch (final SolrServerException ex) {
            Logger.getLogger(SolrService.class.getName()).
                    log(Level.SEVERE, null, ex);
        }

        return null;
    }

    /**
     * Searches the specified keys in the specified fields of the specified type.
     *
     * @param type the specified type
     * @param fieldsAndKeyValues the specified fields and keys
     * @param isHighlight {@code true} will highlight display for the
     * specified field
     * @param start the results from this position
     * @param rows number of the results need to be retrieved
     * @return query response
     */
    public QueryResponse search(final String type,
                                final Map<String, String> fieldsAndKeyValues,
                                final boolean isHighlight,
                                final int start, final int rows) {
        final String query = buildQuery(fieldsAndKeyValues);
        final SolrQuery solrQuery = new SolrQuery();
        solrQuery.setQuery(query + "AND type:" + type);
        solrQuery.setHighlight(isHighlight);
        solrQuery.setStart(start);
        solrQuery.setRows(rows);

        if (isHighlight) {
            solrQuery.setHighlightSnippets(5);
            for (final String field : fieldsAndKeyValues.keySet()) {
                solrQuery.addHighlightField(field);
            }
            solrQuery.setHighlightSimplePre("<font color=\"red\">");
            solrQuery.setHighlightSimplePost("</font>");
        }

        final QueryResponse rsp;

        try {
            return server.query(solrQuery);
        } catch (final SolrServerException ex) {
            Logger.getLogger(SolrService.class.getName()).
                    log(Level.SEVERE, null, ex);
        }

        return null;
    }

    /**
     * Searches the specified key in the specified field of the specified type.
     *
     * @param type the specified type
     * @param field the specified field
     * @param keyValue the specified key
     * @param isHighlight {@code true} will highlight display for the
     * specified field
     * @param start the results from this position
     * @param rows number of the results need to be retrieved
     * @return query response
     */
    public QueryResponse search(final String type, final String field,
                                final String keyValue, final boolean isHighlight,
                                final int start, final int rows) {
        List<String> keys = null;
        try {
            keys = Arrays.asList(spliter.segWords(keyValue, ",").split(","));
        } catch (IOException ex) {
            Logger.getLogger(SolrService.class.getName()).
                    log(Level.SEVERE, null, ex);
        }

        final String query = buildQuery(field, keys);
        final SolrQuery solrQuery =
                new SolrQuery().setQuery(query + "AND type:" + type);
        solrQuery.setHighlight(isHighlight);
        solrQuery.setStart(start);
        solrQuery.setRows(rows);

        if (isHighlight) {
            solrQuery.setHighlightSnippets(2);
            solrQuery.addHighlightField(field);
            solrQuery.setHighlightSimplePre("<font color=\"red\">");
            solrQuery.setHighlightSimplePost("</font>");
        }

        try {
            final QueryResponse rsp = server.query(solrQuery);

            return rsp;
        } catch (final SolrServerException ex) {
            Logger.getLogger(SolrService.class.getName()).
                    log(Level.SEVERE, null, ex);
        }

        return null;
    }

    /**
     * Adds the specified entity to indexing.
     *
     * @param entity the specified entity
     * @return update response
     */
    public UpdateResponse index(final AbstractEntity entity) {
        final SolrInputDocument solrInputDocument =
                server.getBinder().toSolrInputDocument(entity);
        final Entity configuredType = getConfiguredEntity(entity.getClass());
        final List<Prop> configuredProps = configuredType.props;

        if (configuredType.duplicatedUniqueKeyOperation.equals(
                Entity.UNIQUE_KEY_UPDATE_OPERTION)) {
            try {
                for (final Prop prop : configuredProps) {
                    final Field declaredField =
                            entity.getClass().getDeclaredField(prop.name);

                    declaredField.setAccessible(true);
                    Object value = declaredField.get(entity);

                    final PropertyExtractor extractor =
                            getPropertyExtractor(prop.type);
                    extractor.process(prop, value, solrInputDocument);
                }

                final UpdateResponse updateResponse = server.add(
                        solrInputDocument);
                server.commit();

                return updateResponse;
            } catch (final IllegalArgumentException ex) {
                Logger.getLogger(SolrService.class.getName()).
                        log(Level.SEVERE, null, ex);
            } catch (final IllegalAccessException ex) {
                Logger.getLogger(SolrService.class.getName()).
                        log(Level.SEVERE, null, ex);
            } catch (final SolrServerException ex) {
                Logger.getLogger(SolrService.class.getName()).
                        log(Level.SEVERE, null, ex);
            } catch (final IOException ex) {
                Logger.getLogger(SolrService.class.getName()).
                        log(Level.SEVERE, null, ex);
            } catch (final NoSuchFieldException ex) {
                Logger.getLogger(SolrService.class.getName()).
                        log(Level.SEVERE, null, ex);
            } catch (final SecurityException ex) {
                Logger.getLogger(SolrService.class.getName()).
                        log(Level.SEVERE, null, ex);
            }
        } else if (configuredType.duplicatedUniqueKeyOperation.equals(
                Entity.UNIQUE_KEY_PASS_OPERTION)) {
            // currently, do nothing....
        }

        return null;
    }

    /**
     * Gets configured entity of the specified class.
     *
     * @param clazz the specified class
     * @return the configured entity of the specified class, if the specified
     * class has no configured entity, returns <code>null</code>
     */
    private Entity getConfiguredEntity(final Class<?> clazz) {
        for (final Entity entity : Config.solrSvcConfig.entities) {
            if (entity.clazz.equals(clazz.getName())) {
                return entity;
            }
        }

        return null;
    }

    /**
     * Gets configured fields of the specified class.
     *
     * @param clazz the specified class
     * @return the configured fields of the specified class, if the specified
     * class has no configured fields, returns <code>null</code>
     */
    private List<Prop> getConfiguredFields(final Class<?> clazz) {
        List<Prop> props = null;

        for (final Entity entity : Config.solrSvcConfig.entities) {
            if (entity.clazz.equals(clazz.getName())) {
                props = entity.props;
            }
        }

        return props;
    }

    /**
     * Gets the single instance of this service.
     *
     * @return the single instance of this service
     */
    public synchronized static SolrService getInstance() {
        if (instance == null) {
            instance = new SolrService();
        }

        return instance;
    }

    /**
     * Private constructor. Loads the config, initializes the default property
     * extractors, and setup the embedded solr server.
     */
    private SolrService() {
        try {
            Config.load();
            final CoreContainer.Initializer initializer =
                    new CoreContainer.Initializer();

            final CoreContainer coreContainer = initializer.initialize();
            server = new EmbeddedSolrServer(coreContainer, "");
            server.deleteByQuery("*:*"); // delete everything!


            extractors = new ArrayList<PropertyExtractor>();
            addExtractor(new MSWordExtractor());
            addExtractor(new StringExtractor());
            addExtractor(new TextFileExtractor());
            addExtractor(new MSExcelExtractor());
            addExtractor(new PdfExtractor());

            spliter = new Complex(new Dictionary(
                    System.getProperty("solr.solr.home") + "/dic"));

        } catch (final JAXBException ex) {
            Logger.getLogger(SolrService.class.getName()).
                    log(Level.SEVERE, null, ex);
        } catch (SolrServerException ex) {
            Logger.getLogger(SolrService.class.getName()).
                    log(Level.SEVERE, null, ex);
        } catch (final IOException ex) {
            Logger.getLogger(SolrService.class.getName()).
                    log(Level.SEVERE, null, ex);
        } catch (final ParserConfigurationException ex) {
            Logger.getLogger(SolrService.class.getName()).
                    log(Level.SEVERE, null, ex);
        } catch (final SAXException ex) {
            Logger.getLogger(SolrService.class.getName()).
                    log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Adds the specified property extractor for property content process.
     *
     * @param propertyExtractor the specified property extractor
     */
    public void addExtractor(final PropertyExtractor propertyExtractor) {
        extractors.add(propertyExtractor);
    }

    /**
     * Gets a property extractor by the specified property type.
     *
     * @param propertyType the specified property type
     * @return a property extractor by the specified property type, if not
     * found, returns <code>null</code>
     */
    public PropertyExtractor getPropertyExtractor(final String propertyType) {
        for (final PropertyExtractor propertyExtractor : extractors) {
            if (propertyExtractor.getExtractableTypes().contains(propertyType)) {
                return propertyExtractor;
            }
        }

        return null;
    }

    private String buildQuery(final Map<String, String> fieldsAndKeyValues) {
        final StringBuilder sb = new StringBuilder();
        try {
            for (final Map.Entry<String, String> e :
                 fieldsAndKeyValues.entrySet()) {
                final String field = e.getKey();
                final String keyValue = e.getValue();
                List<String> keys = null;
                keys = Arrays.asList(spliter.segWords(keyValue, ",").split(","));
                for (final String key : keys) {
                    sb.append(field + ":" + key + " ");
                }
            }
        } catch (IOException ex) {
            Logger.getLogger(SolrService.class.getName()).
                    log(Level.SEVERE, null, ex);
        }

        return sb.toString();
    }

    private String buildQuery(final String field, final List<String> keys) {
        final StringBuilder sb = new StringBuilder();
        for (final String key : keys) {
            sb.append(field + ":" + key + " ");
        }

        return sb.toString();
    }
}
